/**
 * \file packet_utils.c
 * \brief Utilitaires pour les paquets
 * \author Pierre Neumann / Julien Meutelet
 * \version 0.1
 * \date 20 decembre 2013
 *
 * Fournit différentes fonctions necessaires pour la manipulation de paquet
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h> 
#include <netinet/ip_icmp.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <arpa/inet.h>

#include "packet_utils.h"
#include "packet_ip.h"
#include "packet_icmp.h"
#include "packet_udp.h"
#include "packet_tcp.h"

/**
 * \fn checksum(unsigned short *addr, int length)
 * \brief Calcule la checksum. Référence : RFC 1071 
 *
 * \param addr Pointeur vers l'adresse de départ du calcul de la checksum.
 * \param length Longueur des données à calculer.
 * \return unsigned short Checksum.
 */
unsigned short checksum(unsigned short *addr, int length){ 
	register int sum = 0; 
	u_short res = 0; 
	register u_short *w = addr; 
	register int nleft = length; 

	while (nleft > 1) 
	{ 
	  sum += *w++; 
	  nleft -= 2; 
	} 

	if (nleft == 1) 
	{ 
	  res=0;
	  *(u_char *) (&res) = *(u_char *) w; 
	  sum += res; 
	} 

	sum = (sum >> 16) + (sum & 0xffff);        
	sum += (sum >> 16);          
	res = ~sum;            
	return (res); 
} 

/**
 * \fn checksum_with_pseudoheader(char** packet,size_t protocol_header_size)
 * \brief Calcul de checksum avec pseudo-header pour TCP et UDP
 *
 * \param packet Chaine de caractère représentant le paquet UDP/TCP.
 * \param protocol_header_size Taille de l'entête du protocol + taille des données.
 * \return unsigned short Checksum.
 */
unsigned short checksum_with_pseudoheader(char** packet,size_t protocol_header_size){

	//On récupère l'entête IP et celle du protocole
	struct ip *hip = (struct ip *) *packet;
	char * protocol_header = (char *) (*packet + IP_HEADER_SIZE);
	
	// Taille du pseudo-datagram puis on lui alloue cette taille
	size_t pseudogram_size = sizeof(struct pseudo_header) + protocol_header_size;
	char* pseudogram = (char*)malloc(pseudogram_size); 

	// On initialise le pseudo-header
	struct pseudo_header pseudohdr;
	pseudohdr.source_address = hip->ip_src.s_addr;
	pseudohdr.dest_address = hip->ip_dst.s_addr;
	pseudohdr.placeholder = 0;
	pseudohdr.protocol = hip->ip_p;
	pseudohdr.length = htons(protocol_header_size);
	
	// On ajoute le pseudo-header et l'entête du protocol au pseudo-datagram
	memcpy(pseudogram, (char*) &pseudohdr ,sizeof (struct pseudo_header));
	memcpy(pseudogram + sizeof(struct pseudo_header), protocol_header, protocol_header_size);
	
	// On retourne la checksum du pseudo-datagram
	return checksum((unsigned short*)pseudogram, pseudogram_size);
}

/**
 * \fn get_my_ip(void)
 * \brief Renvoie l'adresse IP de la machine
 *
 * \return Structure in_addr contenant l'adresse IP de la machine.
 */
struct in_addr get_my_ip(void){
	// On crée un socket
	int sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
	if(sockfd == -1){
		 perror("Erreur creation socket");
      		 exit(1);
    	}

	// Taille de la structure sockaddr
	socklen_t addrlen = sizeof(struct sockaddr);

	// Initialise deux structure sockaddr_in
	struct sockaddr_in client;
	client.sin_family = AF_INET;
	struct sockaddr_in client2;
	memset(&client2,0,addrlen);

	// On connecte la socket
	if(connect(sockfd, (struct sockaddr *)&client,addrlen)<0) {
		perror("erreur de la fct connect");
	}
	
	//On récupère la structure sockaddr associé 
	if(getsockname(sockfd, (struct sockaddr *)&client2, &addrlen)==-1){
		perror("erreur de getsockname");
	}
	// On retourne l'adresse IP
	return client2.sin_addr;
}

/**
 * \fn init_ping_result(struct ping_result *res,char** packet,int size)
 * \brief Initialise un résultat de ping
 *
 * \param res Pointeur vers une structure ping_result.
 * \param packet Chaine de caractère représentant le paquet.
 * \param size Taille des données récupérées lors du receivefrom.
 * \param time Temps d'allez-retour en micro-secondes.
 * \return Rien.
 */
void init_ping_result(struct ping_result *res,char** packet,int size,long int time){
	struct ip *hip = (struct ip *) *packet;
	res->protocol = hip->ip_p;
	res->size = size;
	res->time = time;
	res->ttl = hip->ip_ttl; 
	res->addr = hip->ip_src;
	// Selon le protocol on complète le port et le type
	switch(hip->ip_p){
		case 1 : {
			struct icmp *hicmp = (struct icmp *) (*packet + IP_HEADER_SIZE);
			res->port = 0;
			res->type = hicmp->icmp_type*10 + hicmp->icmp_code;
			break;
		};
		case 6 : {
			struct tcphdr *htcp = (struct tcphdr *) (*packet + IP_HEADER_SIZE);
			res->port = ntohs(htcp->source);
			if(htcp->rst && !htcp->ack) res->type = 0;
			else if(!htcp->rst && htcp->ack)res->type = 1; 
			else res = NULL;
			break;
		};
		default : {
			res = NULL;
			break;
		};
	}
}

/**
 * \fn print_packet(char** packet, int length)
 * \brief Affiche en hexadecimal le paquet
 *
 * \param packet Chaine de caractère représentant le paquet.
 * \param length Taille du paquet.
 * \return Rien.
 */
void print_packet(char** packet, int length){
	int i = 0;
	for (i = 0; i <= length;i++)
          	printf("%.2X ",*(*packet+i));
        printf("\n");
}

/**
 * \fn print_details(char** packet)
 * \brief Affiche en détail le contenu du paquet
 *
 * \param packet Chaine de caractère représentant le paquet.
 * \return Rien.
 */
void print_details(char** packet){
	print_ip_header(packet);
}

/**
 * \fn print_ping_result(struct ping_result *res)
 * \brief Affiche le résultat du ping
 *
 * \param res Pointeur vers une structure ping_result.
 * \return Rien.
 */
void print_ping_result(struct ping_result *res){
	printf("%d bits reçus de (%s) : type (",res->size,inet_ntoa(res->addr)); 
	if(res->protocol == 1 && res->type == 0)printf("ECHO REPLY)");
	else if(res->protocol == 1 && res->type == 33)printf("PORT UNREACHABLE)");
	else if(res->protocol == 1 && res->type == 110)printf("TIME EXCEEDED)");
	else if(res->protocol == 6 && res->type)printf("SYN/ACK) port %d actif",res->port);
	else if(res->protocol == 6 && !res->type)printf("RST) port %d inactif",res->port);
	else printf("INCONNU)");
	printf(" : TTL = %d : Temps = %ld us\n",res->ttl,res->time);
}
